/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <plat/config.h>
#include <plat/uart.h>
#include <arch/driver/gic_v2.h>
#include <arch/idle.h>
#include <arch/pagetable.h>
#include <arch/scr.h>
#include <arch/timers.h>
#include <arch/cpsr.h>
#include <arch/cpu.h>
#include <stdlib/string.h>
#include <tools/macros.h>
#include <core/addrspace.h>
#include <core/allocate.h>
#include <core/dprintf.h>
#include <core/interrupt.h>
#include <core/schedule.h>
#include <core/task.h>
#include <caps/capability.h>
#include <caps/cnode.h>
#include <uapi/bootinfo.h>
#include <uapi/ipcbuffer.h>

cptr_t BOOTONLY init_cptr_make(unsigned long guard, unsigned int guard_len, unsigned int radix, unsigned int index)
{
    cptr_t ret = guard;

    if (index >= (1ul << radix)) {
        return 0;
    }

    ret <<= radix;
    ret |= index;

    ret <<= sizeof(cptr_t) * 8 - radix - guard_len;

    return ret;
}
tcb_t *BOOTONLY create_idle_task(void)
{
    extern unsigned long idlestack;
    extern unsigned long rs_ro_start;
    unsigned int         coreid = current_cpu();
#ifdef CONFIG_KERNEL_MCS
    static char task_idle_sc[256 * CONFIG_SMP_CORES];
#endif

    static tcb_t task_idle[CONFIG_SMP_CORES];
    tcb_t       *pidle = task_idle + coreid;
    void        *pstack =
        (char *)&idlestack + coreid * CONFIG_STACK_SIZE - (unsigned long)&rs_ro_start + CONFIG_ROOTSERVER_EXEC_ADDR;

    task_init(pidle, TTYPE_USER);
    tcb_get(pidle);
    task_set_affinity(pidle, current_cpu());
    task_set_stack(pidle, pstack, NULL, CONFIG_STACK_SIZE);
    task_set_state(pidle, TASK_IDLE);
    task_set_name(pidle, "idle");
    task_set_addrspace(pidle, user_addrspace());
    task_set_maxpriority(pidle, CONFIG_MAX_PRIORITY - 1);
    task_set_priority(pidle, CONFIG_MAX_PRIORITY - 1);

#ifdef USE_ONE_STACK
    pidle->stack     = context_get_sp(task_get_context(pidle));
    pidle->p_context = (context_t *)pidle->stack;
#endif

#ifdef CONFIG_KERNEL_MCS
    configure_sched_context(pidle, SC_PTR(task_idle_sc + 256 * coreid), 1 * 100 * 1000, 1 * 100 * 1000, coreid, 0);
#else

#endif
    if (current_cpu() == 0) {
        dprintf("pidle->spel0:[%x,%x] sp_el1:[%x,%x]\n", pidle->context.sp_el0 - CONFIG_STACK_SIZE,
                pidle->context.sp_el0, &pidle->context.spsr, &pidle->context.regs[X30]);
    }
    return pidle;
}

tcb_t *BOOTONLY create_rootserver(uint64_t *initsect_v, uint64_t *initsect_f)
{
    extern unsigned long rs_ro_start;
    static struct stack_ipcbuffer {
        char        stack[CONFIG_STACK_SIZE];
        ipcbuffer_t buf;
        endpoint_t  faulthandler;
    } ALIGNED(CONFIG_STACK_SIZE) rootserver_si __attribute__((section(".rootserver_stack")));
    ;

#ifdef CONFIG_KERNEL_MCS
    static char task_sc[256];
#endif

    void *pstack =
        (void *)((unsigned long)rootserver_si.stack - (unsigned long)&rs_ro_start + CONFIG_ROOTSERVER_EXEC_ADDR);
    void *ipcbuff       = pstack + CONFIG_STACK_SIZE;
    void *fault_handler = ipcbuff + sizeof(ipcbuffer_t);

    static tcb_t rootserver[1];

    bootinfo_t *bi = (bootinfo_t *)ipcbuff;

    bi->ipcbuffer    = bi;
    bi->ipcbufferlen = sizeof(ipcbuffer_t);
    bi->stack        = pstack;
    bi->stacklen     = CONFIG_STACK_SIZE;
    bi->radix        = CONFIG_INITIAL_CNODE_RADIX;
    bi->guardlen     = 4;
    bi->guard        = lowbitsmask(bi->guardlen);
    bi->freeindex    = CAP_INDEX_COUNT;
    int i;
    for (i = 0; i < MAX_RAM_INFO; i++) {
        if (!initsect_v[i]) {
            break;
        }
        bi->initsect[i].vbase = initsect_v[i];
        bi->initsect[i].size  = (initsect_f[i] & 0xFFFFFFFFU);
        bi->initsect[i].flag  = (initsect_f[i] >> 32);
        dprintf("bi->initsect[i].vbase = %x size = %x flag = %x \n", bi->initsect[i].vbase, bi->initsect[i].size,
                bi->initsect[i].flag);
    }
    const unsigned int *sizes = cap_get_capobj_size();

    bi->caps_size = (void *)(tran_kernel_to_user((unsigned long)sizes));
    dprintf("create bi addr: %x,ipcbuffer addr: %x\n", bi, bi->ipcbuffer);

    task_init(rootserver, TTYPE_USER);
    dprintf("bi->caps_size: %x \n", bi->caps_size);
    rootserver->timeslice.timeleft = rootserver->timeslice.timeslice;
    tcb_get(rootserver);
    task_set_entry(rootserver, CONFIG_ROOTSERVER_ENTRY_ADDR);
    task_set_stack(rootserver, pstack, ipcbuff, CONFIG_STACK_SIZE);
    task_set_retcode(rootserver, (unsigned long)bi);
    task_set_state(rootserver, TASK_READY);
    task_set_name(rootserver, "rootserver");
    task_set_addrspace(rootserver, user_addrspace());
#if THREAD_GRAB
    task_set_maxpriority(rootserver, CONFIG_PRIORITY_ROOTSERVER);
    task_set_priority(rootserver, CONFIG_PRIORITY_ROOTSERVER);
#else
    task_set_maxpriority(rootserver, CONFIG_PRIORITY_ROOTSERVER);
    task_set_priority(rootserver, CONFIG_PRIORITY_ROOTSERVER);
#endif
    task_set_domain(rootserver, 0);
    task_set_affinity(rootserver, 0);

    endpoint_init((endpoint_t *)fault_handler);
    endpoint_get((endpoint_t *)fault_handler);
    task_set_faulthandler(rootserver, (endpoint_t *)fault_handler);

    capability_t *k_cspace =
        (capability_t *)initial_cspace_init(rootserver, user_addrspace(), (endpoint_t *)fault_handler);

    bi->cspace = (void *)(tran_kernel_shared_bss_to_user((unsigned long)k_cspace));
    cap_cnode_init(task_get_cnode(rootserver), k_cspace, bi->guardlen, bi->guard, bi->radix);

#ifdef USE_ONE_STACK
    rootserver->stack               = context_get_sp(task_get_context(rootserver));
    rootserver->p_context           = (context_t *)(rootserver->stack - sizeof(context_t));
    rootserver->p_context->regs[SP] = context_get_sp(task_get_context(rootserver));
    rootserver->p_context->elr      = context_get_pc(task_get_context(rootserver));
#endif
#ifdef CONFIG_KERNEL_MCS
    configure_sched_context(rootserver, SC_PTR(task_sc), 1 * 10000 * 10000, 1 * 10000 * 10000, 0, 0);
#endif
    rootserver->cptr = init_cptr_make(bi->guard, bi->guardlen, bi->radix, CAP_INDEX_THREAD);
    schedule_attach(rootserver);
    dprintf("rootserver->spel0:[%x,%x] sp_el1:[%x,%x]\n", pstack, pstack + CONFIG_STACK_SIZE, &rootserver->context.spsr,
            &rootserver->context.regs[X30]);
    return rootserver;
}

void tlbi_test(void)
{
    unsigned long cnt = 64;
    while (cnt--) {
        unsigned long value = 64 - cnt;
        dprintf("tlbi %d times\n", value);
        value = 1;
        value <<= 48;
        asm volatile("tlbi aside1,%0\n\t"
                     "dsb nsh\n\t"
                     "isb\n\t"
                     "dsb sy\n\t" ::"r"(value)
                     : "memory");
    }
}

void BOOTONLY init_kernel(void)
{
    dprintf("start init kernel!\n");

    cpsr_dump_info();
    cpu_init();

    set_available_lock_flag(1);
    if (current_cpu() == 0) {
        gicd_init();
    }

    domain_init(CONFIG_START_DOMAIN);

    uint64_t initsect_v[MAX_RAM_INFO];
    uint64_t initsect_f[MAX_RAM_INFO];
    (void)memset(initsect_v, 0, sizeof(uint64_t) * MAX_RAM_INFO);
    (void)memset(initsect_f, 0, sizeof(uint64_t) * MAX_RAM_INFO);

    allocator_init(initsect_v, initsect_f);

    kernel_addrspace_init();
    rootserver_addrspace_init(initsect_v, initsect_f);

    irq_handler_init();
    schedule_init(create_idle_task());
    timer_datas_init(current_cpu());

    pt_init();
    (void)create_rootserver(initsect_v, initsect_f);

    dprintf("end init kernel!\n");

    boottime_calculate();
}
